package com.wangcanxuan.www.proxy;

import com.wangcanxuan.www.annotation.exception.ExceptionHandler;
import com.wangcanxuan.www.entity.dto.ResultInfo;
import com.wangcanxuan.www.util.LoggerUtil;

import java.lang.reflect.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;

/**
 * 功能代理，进行mapper的注入，controller层的统一日志打印及统一异常处理，Service层的事务处理
 *
 * @author HHLJ
 * @date 2022/03/18
 */
public class ControllerProxy implements InvocationHandler {


    /**
     * 日志记录器
     */
    private static final Logger logger = LoggerUtil.getLogger();

    /**
     * 代理目标
     */
    private Object target;

    /**
     * 代理对象
     */
    private Object proxy;

    /**
     * 该map相当于一个对本代理类的缓存，也算是一种单例模式，防止生成过多的代理类
     * 假如我多次调用被代理的类，只用创建第一次代理对象，在以后调用的时候会从map集合里
     * 拿出对应我被代理类的class对象的FunctionProxy对象
     * 使用ReentrantLock保证并发情况下的并发安全，将返回写在try-finally外减小锁的粒度
     */
    private static final HashMap<Class<?>, ControllerProxy> INVOCATION_HANDLER_HASH_MAP = new HashMap<>();

    /**
     * 锁
     */
    private static final Lock LOCK = new ReentrantLock();

    /**
     * 创建代理对象
     * 代理对象proxy的类型是com.sun.proxy.$Proxy0
     *
     * @param proxyClazz 代理clazz
     * @return {@link T}
     */
    public static <T> T getProxyInstance(Class<T> proxyClazz) {
        // 从集合中获取invocationHandler，即FunctionProxy对象
        ControllerProxy invocationHandler = INVOCATION_HANDLER_HASH_MAP.get(proxyClazz);
        LOCK.lock();
        try {
            // 如果invocationHandler为空，为其初始化
            if (invocationHandler == null) {
                invocationHandler = new ControllerProxy();
                // 创建一个实例对象
                try {
                    T tar = proxyClazz.newInstance();
                    invocationHandler.setTarget(tar);
                    invocationHandler.setProxy(Proxy.newProxyInstance(tar.getClass().getClassLoader(), tar.getClass().getInterfaces(), invocationHandler));
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
                // 在map中put被代理类的类对象和代理类的对象即invocationHandler
                INVOCATION_HANDLER_HASH_MAP.put(proxyClazz, invocationHandler);
            }
        } finally {
            LOCK.unlock();
        }
        return (T) invocationHandler.getProxy();
    }


    /**
     * newProxyInstance方法,创建了代理对象，自动生成代理类$proxy
     * 比如被代理的接口为UserMapper，则target是被代理的对象即UserMapperImpl，args则是传入的参数，method是经反射获取到的方法
     * $proxy extends Proxy implements UserMapper
     *
     * @param proxy  代理对象
     * @param method 真实对象的某方法
     * @param args   方法参数
     * @return {@link Object}
     * @throws Throwable throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        // 如果实现类方法有ExceptionHandler注解，进行拦截并异常处理做日志打印
        if (method.isAnnotationPresent(ExceptionHandler.class)) {
            // 前置日志打印传入参数
            begin(method, args);
            try {
                result = method.invoke(target, args);
            } catch (Exception e) {
                exception(method, e);
            }
            // 后置日志打印返回结果
            if (result != null) {
                end(method, result);
            }
            return result;
        }
        return method.invoke(target, args);
    }


    private ControllerProxy() {

    }

    private void setTarget(Object target) {
        this.target = target;
    }

    private Object getProxy() {
        return proxy;
    }

    private void setProxy(Object proxy) {
        this.proxy = proxy;
    }


    /**
     * 方法开始时打印日志
     *
     * @param method 方法
     * @param args   arg游戏
     */
    private void begin(Method method, Object[] args) {
        logger.info(target.getClass().getName() + "." + method.getName() + " 启动" + " 方法参数: " +
                (null == args ? "null" : Arrays.asList(args).toString()));
    }

    /**
     * 方法结束时打印日志
     *
     * @param method 方法
     * @param result 结果
     */
    private void end(Method method, Object result) {
        if (result instanceof ResultInfo<?>) {
            ResultInfo<?> resultMessage = (ResultInfo<?>) result;

            String message = resultMessage.getMessage();
            Object obj = resultMessage.getObj();

            logger.info(target.getClass().getName() + "." + method.getName() + " 结束" + " 返回参数为:" + "[" + "," + message + "," + obj + "]");

        }
    }

    /**
     * 方法出异常时打印日志
     *
     * @param method 方法
     * @param e      e
     */
    private void exception(Method method, Exception e) {
        logger.severe("位置：" + target.getClass().getName() + "." + method.getName() + " 发生了异常：" + e.getCause());
        logger.severe("异常结束");
    }
}
